package com.xiaoyu.base.utils;

import androidx.annotation.Nullable;

import in.srain.cube.util.internal.AppObserver;
import in.srain.cube.util.internal.AppSupplier;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;

import io.reactivex.Maybe;
import io.reactivex.Observable;
import io.reactivex.functions.BiFunction;
import io.reactivex.functions.Function;
import io.reactivex.functions.Predicate;

public final class ListUtil {

    public static <T> T getFirst(final List<T> list) {
        return getFirst(list, null);
    }

    public static <T> T getOrDefault(final List<T> list, int i, T defaultValue) {
        return isHasIndex(list, i) ? list.get(i) : defaultValue;
    }

    public static <T> T getLast(final List<T> list) {
        return getLast(list, null);
    }

    public static <T> T getFirst(final List<T> list, final T t) {
        return isNullOrEmpty(list) ? t : list.get(0);
    }

    public static <T> T getLast(final List<T> list, final T t) {
        return isNullOrEmpty(list) ? t : list.get(list.size() - 1);
    }

    public static <T> T getRandom(final List<T> list, final T t) {
        return isNullOrEmpty(list) ? t : list.get(new Random().nextInt(list.size()));
    }

    public static <T> boolean isNullOrEmpty(final List<T> list) {
        return list == null || list.isEmpty();
    }

    public static <T> boolean isHasIndex(final List<T> list, final int i) {
        return list != null && i >= 0 && i < list.size();
    }

    public static <T> boolean contains(final List<T> list, final T t) {
        return !isNullOrEmpty(list) && list.contains(t);
    }

    public static <I> List<I> filterList(Collection<I> inputs, Predicate<? super I> predicate) {
        return Observable.fromIterable(inputs).filter(predicate).toList().blockingGet();
    }

    public static <I> List<I> filterNull(Collection<I> inputs) {
        return Observable.fromIterable(inputs).filter(i -> i != null).toList().blockingGet();
    }

    public static <I, O> List<O> convertToList(Collection<I> inputs, Function<I, O> mapper) {
        return Observable.fromIterable(inputs).map(mapper).toList().blockingGet();
    }

    public static <I, O> List<O> convertToListWithIndex(Collection<I> inputs, BiFunction<I, Integer, O> mapper) {
        return Observable.fromIterable(inputs).zipWith(Observable.range(0, Integer.MAX_VALUE), mapper).toList().blockingGet();
    }

    public static <I, O> List<O> convertToListFilterNull(Collection<I> inputs, Function<I, O> mapper) {
        return Observable.fromIterable(inputs).map(i -> RxUtils.ofNullable(mapper.apply(i))).filter(maybe -> !maybe.isEmpty().blockingGet()).map(Maybe::blockingGet).toList().blockingGet();
    }

    public static <I, O> List<O> convertToList(Collection<I> inputs, Function<I, O> mapper, Predicate<? super O> predicate) {
        return Observable.fromIterable(inputs).map(mapper).filter(predicate).toList().blockingGet();
    }

    public static <I, O> List<O> convertToList(Collection<I> inputs, Predicate<? super I> predicate, Function<I, O> mapper) {
        return Observable.fromIterable(inputs).filter(predicate).map(mapper).toList().blockingGet();
    }

    public static <I, O> Set<O> convertToSet(Collection<I> inputs, Function<I, O> mapper) {
        return Observable.fromIterable(inputs).map(mapper).collectInto(new HashSet<O>(), HashSet::add).blockingGet();
    }

    public static <I, O> Set<O> convertToSetFilterNull(Collection<I> inputs, Function<I, O> mapper) {
        return Observable.fromIterable(inputs).map(i -> RxUtils.ofNullable(mapper.apply(i))).filter(maybe -> !maybe.isEmpty().blockingGet()).map(Maybe::blockingGet).collectInto(new HashSet<O>(), HashSet::add).blockingGet();
    }

    public static <I, K> Map<K, I> convertToMap(Collection<I> inputs, Function<I, K> keySelector) {
        return Observable.fromIterable(inputs).toMap(keySelector).blockingGet();
    }

    public static <I, K, V> Map<K, V> convertToMap(Collection<I> inputs, Function<I, K> keySelector, Function<I, V> valueSelector) {
        return Observable.fromIterable(inputs).toMap(keySelector, valueSelector).blockingGet();
    }

    public static <I, K> Map<K, Collection<I>> convertToMultimap(Collection<I> inputs, Function<I, K> keySelector) {
        return Observable.fromIterable(inputs).toMultimap(keySelector).blockingGet();
    }

    public static <I, K, V> Map<K, Collection<V>> convertToMultimap(Collection<I> inputs, Function<I, K> keySelector, Function<I, V> valueSelector) {
        return Observable.fromIterable(inputs).toMultimap(keySelector, valueSelector).blockingGet();
    }

    public static <I, O> List<O> convertListWithIndex(List<I> inputs, BiFunction<I, Integer, O> mapper) {
        return Observable.fromIterable(inputs).zipWith(Observable.range(0, Integer.MAX_VALUE), mapper).toList().blockingGet();
    }

    public static <T> void shrinkTo(final List<T> list, int newSize) {
        if (newSize < list.size()) {
            list.subList(newSize, list.size()).clear();
        }
    }

    public static <T> List<T> emptyIfNull(List<T> list) {
        return list != null ? list : Collections.emptyList();
    }

    public static <T> void forEach(List<T> list, AppObserver<T> observer) {
        for (T t : list) {
            observer.onEvent(t);
        }
    }

    public static <T> void batchProcess(List<T> list, int batchSize, AppObserver<List<T>> observer) {
        batchProcess(list, batchSize, observer, null);
    }

    public static <T> void batchProcess(List<T> list, int batchSize, AppObserver<List<T>> observer, @Nullable AppSupplier<Boolean> predicate) {
        final int size = list.size();
        for (int i = 0; i < size; i += batchSize) {
            if (predicate != null && predicate.get()) {
                observer.onEvent(new ArrayList<>(list.subList(i, Math.min(size, i + batchSize))));
            }
        }
    }
}
